package com.felix.apigw;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
@Slf4j
public class DynamicRouteService implements ApplicationEventPublisherAware {
    private static final List<String> ROUTE_LIST = new ArrayList<>();
    private final ObjectMapper objectMapper = new ObjectMapper();
    final Configuration configuration = Configuration.builder()
            .jsonProvider(new JacksonJsonProvider(objectMapper))
            .mappingProvider(new JacksonMappingProvider(objectMapper))
            .build();
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher applicationEventPublisher;

    ExecutorService executor = Executors.newSingleThreadExecutor();

    @Value("${gateway.base.path:}")
    private String basePath;
    @Value("${gateway.config.file:gateway.json}")
    private String gatewayConfigFile;

    @PostConstruct
    public void dynamicRouteListener() {
        executor.submit(() -> {
            try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
                // 初始化加载
                refresh();
                final Path from = Paths.get(basePath);
                //给path路径加上文件观察服务
                from.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_MODIFY,
                        StandardWatchEventKinds.ENTRY_DELETE);
                while (true) {
                    final WatchKey key = watchService.take();
                    for (WatchEvent<?> watchEvent : key.pollEvents()) {
                        final WatchEvent.Kind<?> kind = watchEvent.kind();
                        // get the filename for the event
                        final WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;
                        final Path filename = watchEventPath.context();
                        if (kind == StandardWatchEventKinds.OVERFLOW || !StringUtils.equals(filename.toString(), gatewayConfigFile)) {
                            continue;
                        }
                        //修改事件
                        if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
                            refresh();
                        }
                    }
                    boolean valid = key.reset();
                    if (!valid) {
                        break;
                    }
                    Thread.sleep(1000);
                }
            } catch (IOException | InterruptedException ex) {
                System.err.println(ex);
            }
        });
    }


    public void refresh() throws IOException {
        String configInfo = ConfigJsonParser.getConfig(new File(basePath, gatewayConfigFile));
        try {
            if (StringUtil.isNullOrEmpty(configInfo)) {//配置被删除
                return;
            }
            List<RouteDefinition> gatewayRouteDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
            });
            clearRoute();
            for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
                addRoute(routeDefinition);
            }
            log.info("refresh {} 条router :{}", gatewayRouteDefinitions.size(), ROUTE_LIST);
            publish();
        } catch (Exception e) {
            log.error("receiveConfigInfo error", e);
        }
    }

    private void clearRoute() {
        for (String id : ROUTE_LIST) {
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        }
        ROUTE_LIST.clear();
    }

    private void addRoute(RouteDefinition definition) {
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            ROUTE_LIST.add(definition.getId());
        } catch (Exception e) {
            log.error("addRoute error" + e);
        }
    }

    private void publish() {
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}
